@file:Suppress("SENSELESS_COMPARISON")

package com.andy.logcat

import android.util.Log
import com.andy.logcat.LogUtils.Type.*
import com.andy.logcat.LogUtils.enabled
import com.andy.logcat.LogUtils.logHooks
import com.andy.logcat.LogUtils.tag
import org.json.JSONArray
import org.json.JSONObject
import org.json.JSONTokener
import kotlin.math.min

/**
 * @property tag 默认日志标签
 * @property enabled 日志全局开关
 * @property logHooks 日志拦截器
 */
object LogUtils {

    private const val SEGMENT_SIZE = 3 * 1024

    enum class Type {
        VERBOSE, DEBUG, INFO, WARN, ERROR, WTF
    }

    /** 日志默认标签 */
    var tag = "Nature"

    /** 是否启用日志 */
    var enabled = true

    /** 日志是否显示代码位置 */
    var traceEnabled = true

    /** 日志的Hook钩子 */
    val logHooks by lazy { ArrayList<LogHook>() }

    /**
     * @param enabled 是否启用日志
     * @param tag 日志默认标签
     */
    fun setDebug(enabled: Boolean, tag: String = this.tag) {
        this.enabled = enabled
        this.tag = tag
    }

    /**
     * 添加日志拦截器
     */
    fun addHook(hook: LogHook) {
        logHooks.add(hook)
    }

    /**
     * 删除日志拦截器
     */
    fun removeHook(hook: LogHook) {
        logHooks.remove(hook)
    }

    @JvmOverloads
    @JvmStatic
    fun v(
        msg: Any?,
        tr: Throwable? = null,
        occurred: Throwable? = Exception()
    ) {
        print(VERBOSE, tag, msg, tr, occurred)
    }

    @JvmOverloads
    @JvmStatic
    fun v(
        tag: String,
        msg: Any?,
        tr: Throwable? = null,
        occurred: Throwable? = Exception()
    ) {
        print(VERBOSE, tag, msg, tr, occurred)
    }

    @JvmOverloads
    @JvmStatic
    fun i(
        msg: Any?,
        tr: Throwable? = null,
        occurred: Throwable? = Exception()
    ) {
        print(INFO, tag, msg, tr, occurred)
    }

    @JvmOverloads
    @JvmStatic
    fun i(
        tag: String,
        msg: Any?,
        tr: Throwable? = null,
        occurred: Throwable? = Exception()
    ) {
        print(INFO, tag, msg, tr, occurred)
    }

    @JvmOverloads
    @JvmStatic
    fun d(
        msg: Any?,
        tr: Throwable? = null,
        occurred: Throwable? = Exception()
    ) {
        print(DEBUG, tag, msg, tr, occurred)
    }

    @JvmOverloads
    @JvmStatic
    fun d(
        tag: String,
        msg: Any?,
        tr: Throwable? = null,
        occurred: Throwable? = Exception()
    ) {
        print(DEBUG, tag, msg, tr, occurred)
    }

    @JvmOverloads
    @JvmStatic
    fun w(
        msg: Any?,
        tr: Throwable? = null,
        occurred: Throwable? = Exception()
    ) {
        print(WARN, tag, msg, tr, occurred)
    }

    @JvmOverloads
    @JvmStatic
    fun w(
        tag: String,
        msg: Any?,
        tr: Throwable? = null,
        occurred: Throwable? = Exception()
    ) {
        print(WARN, tag, msg, tr, occurred)
    }

    @JvmOverloads
    @JvmStatic
    fun e(
        msg: Any?,
        tr: Throwable? = null,
        occurred: Throwable? = Exception()
    ) {
        print(ERROR, tag, msg, tr, occurred)
    }

    @JvmOverloads
    @JvmStatic
    fun e(
        tag: String,
        msg: Any?,
        tr: Throwable? = null,
        occurred: Throwable? = Exception()
    ) {
        print(ERROR, tag, msg, tr, occurred)
    }

    @JvmOverloads
    @JvmStatic
    fun wtf(
        msg: Any?,
        tr: Throwable? = null,
        occurred: Throwable? = Exception()
    ) {
        print(WTF, tag, msg, tr, occurred)
    }

    @JvmOverloads
    @JvmStatic
    fun wtf(
        tag: String,
        msg: Any?,
        tr: Throwable? = null,
        occurred: Throwable? = Exception()
    ) {
        print(WTF, tag, msg, tr, occurred)
    }

    /**
     * 输出日志
     * 如果[msg]和[occurred]为空或者[tag]为空将不会输出日志, 拦截器
     *
     * @param type 日志等级
     * @param msg 日志信息
     * @param tag 日志标签
     * @param occurred 日志异常
     */
    private fun print(type: Type = INFO, tag: String, msg: Any? = null, tr: Throwable? = null, occurred: Throwable? = Exception()) {
        if (!enabled || msg == null) {
            return
        }
        var message = msg.toString()
        val info = LogInfo(type, message, tag, tr, occurred)
        for (logHook in logHooks) {
            logHook.hook(info)
            if (info.msg == null) {
                return
            }
        }
        if (traceEnabled && occurred != null) {
            occurred.stackTrace.getOrNull(1)?.run {
                message += " ...($fileName:$lineNumber)"
            }
        }
        val length = message.length
        if (length > SEGMENT_SIZE) {
            synchronized(this) {
                var startIndex = 0
                var endIndex = SEGMENT_SIZE
                while (startIndex < length) {
                    endIndex = min(length, endIndex)
                    val substring = message.substring(startIndex, endIndex)
                    log(type, substring, tag, tr)
                    startIndex += SEGMENT_SIZE
                    endIndex += SEGMENT_SIZE
                }
            }
        } else {
            log(type, message, tag, tr)
        }
    }

    /**
     * JSON格式化输出日志
     * @param msg 日志信息
     * @param type 日志类型
     * @param occurred 日志发生位置
     */
    @JvmOverloads
    @JvmStatic
    fun json(json: Any?, msg: String = "", type: Type = INFO, occurred: Throwable? = Exception()) {
        if (!enabled || json == null) {
            return
        }
        var message = json.toString()
        val occurredMsg = if (traceEnabled && occurred != null) {
            occurred.stackTrace.getOrNull(1)?.run { " ($fileName:$lineNumber)" }
        } else {
            ""
        }
        if (message.isBlank()) {
            print(type, "$msg$occurredMsg\n$message", tag, occurred = null)
            return
        }
        val parseMessage = JSONTokener(message)
        val obj = try {
            parseMessage.nextValue()
        } catch (e: Exception) {
            "Parse json error"
        }
        message = when (obj) {
            is JSONObject -> obj.toString(2)
            is JSONArray -> obj.toString(2)
            else -> obj.toString()
        }
        print(type, "$msg$occurredMsg\n$message", tag, occurred = null)
    }

    /**
     * JSON格式化输出日志
     * @param tag 日志标签
     * @param msg 日志信息
     * @param type 日志类型
     * @param occurred 日志发生位置
     */
    @JvmOverloads
    @JvmStatic
    fun json(tag: String, json: Any?, msg: String = "", type: Type = INFO, occurred: Throwable? = Exception()) {
        if (!enabled || json == null) {
            return
        }
        var message = json.toString()
        val occurredMsg = if (traceEnabled && occurred != null) {
            occurred.stackTrace.getOrNull(1)?.run { " ($fileName:$lineNumber)" }
        } else {
            ""
        }
        if (message.isBlank()) {
            print(type, "$msg$occurredMsg\n$message", tag, occurred = null)
            return
        }
        val parseMessage = JSONTokener(message)
        val obj = try {
            parseMessage.nextValue()
        } catch (e: Exception) {
            "Parse json error"
        }
        message = when (obj) {
            is JSONObject -> {
                obj.toString(2)
            }
            is JSONArray -> {
                obj.toString(2)
            }
            else -> {
                obj.toString()
            }
        }
        print(type, "$msg$occurredMsg\n$message", tag, occurred = null)
    }

    private fun log(type: Type, msg: String, tag: String, tr: Throwable?) {
        when (type) {
            VERBOSE -> Log.v(tag, msg, tr)
            DEBUG -> Log.d(tag, msg, tr)
            INFO -> Log.i(tag, msg, tr)
            WARN -> Log.w(tag, msg, tr)
            ERROR -> Log.e(tag, msg, tr)
            WTF -> Log.wtf(tag, msg, tr)
        }
    }
}